// tnxet.js
/**
 * 对Electron渲染进程的扩展支持
 */
import tnxcore from '../../tnxcore/src/tnxcore';
import SafePromise from '../../tnxcore/src/SafePromise';
import './tnxet.css';
import common from './tnxet-common.js';

export const build = tnxcore.build;

function ipcRequest(eventName, args = []) {
    const {ipcRenderer} = require('electron'); // 放在此处以使得在浏览器中可以初始化运行
    return new SafePromise((resolve, reject) => {
        let requestId = window.tnx.util.string.uuid32();
        const listener = (event, responseId, error, ...result) => {
            if (requestId === responseId) {
                ipcRenderer.off(eventName.response, listener);
                if (error) {
                    console.error(error);
                    if (Array.isArray(error)) {
                        error = error[0];
                    }
                    if (error instanceof Error) {
                        error = error.message || error.toString();
                    }
                    reject(error); // reject只能接受一个参数
                } else {
                    // resolve只能接受一个参数，当有多个参数时，做为数组传递
                    resolve(result.length > 1 ? result : result[0]);
                }
            }
        };
        ipcRenderer.on(eventName.response, listener);
        ipcRenderer.send(eventName.request, requestId, ...args);
    }, error => {
        window.tnx.error(error);
    });
}

export default build('tnxet', () => {
    const tnxet = Object.assign({}, tnxcore, {});

    window.tnx.app.rpc.addEnumType({
        name: 'Boolean',
        items: [{
            key: 'true',
            caption: '是',
        }, {
            key: 'false',
            caption: '否',
        }],
    });

    Object.assign(tnxet.util.file, {
        separator: navigator.userAgent.contains('Win') ? '\\' : '/',
        joinPath(...paths) {
            let result = '';
            if (paths.length) {
                for (let path of paths) {
                    if (path.startsWith(this.separator)) {
                        path = path.substring(this.separator.length);
                    }
                    if (path.endsWith(this.separator)) {
                        result += path;
                    } else {
                        result += path + this.separator;
                    }
                }
                result = result.substring(0, result.length - 1);
            }
            return result;
        },
        /**
         * 获取指定路径的上级目录路径，含末尾的斜杠，以示该地址为目录而不是文件
         * @param path 路径
         * @returns {null|string} 上级目录路径，如果没有上级目录，则返回null
         */
        getParentDir(path) {
            if ((this.separator === '\\' && !path.endsWith(':\\')) || (this.separator === '/' && path !== '/')) {
                let index = path.lastIndexOf(this.separator);
                if (index >= 0) {
                    if (index === path.length - 1) { // 已斜杠结尾，则先去掉斜杠，以便于后续判断
                        path = path.substring(0, index);
                        index = path.lastIndexOf(this.separator);
                    }
                    if (index >= 0) {
                        return path.substring(0, index + 1);
                    }
                }
            }
            return null;
        },
        toRelativePath(basePath, baseIsFile, absolutePath) {
            // 如果基础路径就是绝对路径的开头,直接截取后面的部分
            if (absolutePath.startsWith(basePath)) {
                let relativePath = absolutePath.substring(basePath.length);
                // 去掉开头的分隔符
                return relativePath.startsWith(this.separator) ? relativePath.substring(1) : relativePath;
            }

            // 将两个路径都分割成数组
            const absoluteSegments = absolutePath.split(this.separator);
            const baseSegments = basePath.split(this.separator);

            // 找到第一个不同的位置
            let i = 0;
            while (i < absoluteSegments.length && i < baseSegments.length && absoluteSegments[i] === baseSegments[i]) {
                i++;
            }

            // 构建相对路径
            let relativeSegmentLength = baseSegments.length - i;
            if (baseIsFile) {
                relativeSegmentLength--;
            }
            const relativeSegments = Array(relativeSegmentLength).fill('..')
                .concat(absoluteSegments.slice(i));

            // 使用'/'拼接最终结果
            return relativeSegments.join('/');
        },
        toAbsolutePath(basePath, baseIsFile, relativePath) {
            // 将基础路径和相对路径分割成段
            const baseSegments = basePath.split(this.separator);
            const relativeSegments = relativePath.replace(/\\/g, '/').split('/');

            // 创建结果数组，初始化为基础路径的段
            let resultSegments = baseIsFile ? baseSegments.slice(0, baseSegments.length - 1) : [...baseSegments];

            // 处理每个相对路径段
            for (const segment of relativeSegments) {
                if (segment === '..') {
                    // 如果遇到'..'，移除结果中的最后一段
                    if (resultSegments.length > 0) {
                        resultSegments.pop();
                    }
                } else if (segment !== '.' && segment !== '') {
                    // 如果不是'.'或空字符串，添加到结果中
                    resultSegments.push(segment);
                }
            }

            // 使用系统分隔符拼接最终结果
            return resultSegments.join(this.separator);
        },
        getFileName(path) {
            let index = path.lastIndexOf(this.separator);
            // 斜杠不是在末尾，才有文件名
            if (index < path.length - 1) {
                return path.substring(index + 1);
            }
            return null;
        },
        loadSeparator() {
            return ipcRequest(common.event.file.separator, []).then(separator => {
                this.separator = separator;
                return separator;
            });
        },
        exists(path) {
            return ipcRequest(common.event.file.exists, [path]);
        },
        read(path, withMeta = false, dataIfNonexistent) {
            return ipcRequest(common.event.file.read, [path, withMeta, dataIfNonexistent]);
        },
        write(path, data) {
            return ipcRequest(common.event.file.write, [path, data]);
        },
        readAll(dir, pathRegex) {
            return ipcRequest(common.event.file.readAll, [dir, pathRegex]);
        },
        findPath(dir, pathRegex) {
            return ipcRequest(common.event.file.findPath, [dir, pathRegex]);
        },
        delete(path, deleteEmptyDir = false, throwIfAbsent = false) {
            return ipcRequest(common.event.file.delete, [path, deleteEmptyDir, throwIfAbsent]);
        },
        copy(sourceFilePath, targetFilePath, force) {
            return ipcRequest(common.event.file.copy, [sourceFilePath, targetFilePath, force]);
        },
    });

    tnxet.util.dialog = {
        open(options) {
            return ipcRequest(common.event.dialog.open, [options]);
        },
        save(options) {
            return ipcRequest(common.event.dialog.save, [options]);
        },
    };

    tnxet.util.zip = {
        filenames(zipPath) {
            return ipcRequest(common.event.zip.filenames, [zipPath]);
        },
        read(zipPath, filename) {
            return ipcRequest(common.event.zip.read, [zipPath, filename]);
        },
        readAll(zipPath, filenamePattern) {
            return ipcRequest(common.event.zip.readAll, [zipPath, filenamePattern]);
        },
    }

    tnxet.util.clipboard = {
        write(text) {
            return ipcRequest(common.event.clipboard.write, [text]);
        },
    }

    tnxet.util.xml = {
        containsTag(xml, tagPath) {
            const range = this._findTagPathRange(xml, tagPath);
            return range !== null;
        },
        /**
         * 在 xml 中查找按路径（如 a.b.c，不含根标签）指定的第一个目标标签节点的范围
         * @param xml xml 字符串
         * @param path 路径型标签（以点分隔的层级，不含根）
         * @returns {[number, number]|null} 目标节点的 [start, endExclusive] 范围，找不到返回 null
         * @private
         */
        _findTagPathRange(xml, path) {
            if (!path || typeof path !== 'string') {
                return null;
            }
            const tags = path.split('.').filter(Boolean);
            if (tags.length === 0) {
                return null;
            }

            // 获取根内容范围
            const rootRange = this._findRootContentRange(xml);
            if (!rootRange) {
                return null;
            }
            let containerStart = rootRange[0];
            let containerEnd = rootRange[1];
            let range = null;

            for (let i = 0; i < tags.length; i++) {
                const tag = tags[i];
                range = this._findDirectChildRange(xml, tag, containerStart, containerEnd);
                if (!range) {
                    return null;
                }
                // 下一层在当前标签的内容区继续查找
                const openTokenLen = ('<' + tag + '>').length;
                containerStart = range[0] + openTokenLen;
                const closeTokenLen = ('</' + tag + '>').length;
                containerEnd = range[1] - closeTokenLen;
            }
            return range;
        },
        /**
         * 查找根标签的内容范围（不含开闭标签）
         * @returns {[number, number]|null}
         * @private
         */
        _findRootContentRange(xml) {
            let i = 0;
            while (i < xml.length) {
                let lt = xml.indexOf('<', i);
                if (lt < 0) {
                    return null;
                }
                if (xml.startsWith('<?', lt) || xml.startsWith('<!', lt)) {
                    i = lt + 2;
                    continue;
                }
                if (xml.startsWith('</', lt)) {
                    i = lt + 2;
                    continue;
                }
                // 读取根标签名
                let j = lt + 1;
                let name = '';
                while (j < xml.length) {
                    const ch = xml[j];
                    if (ch === '>' || /\s/.test(ch)) {
                        break;
                    }
                    name += ch;
                    j++;
                }
                if (!name) {
                    return null;
                }
                const openEnd = xml.indexOf('>', j);
                if (openEnd < 0) {
                    return null;
                }
                const closeToken = '</' + name + '>';
                const closeIdx = xml.indexOf(closeToken, openEnd + 1);
                if (closeIdx < 0) {
                    return null;
                }
                return [openEnd + 1, closeIdx];
            }
            return null;
        },
        /**
         * 在指定容器范围内查找给定标签的直接子节点范围
         * @param xml
         * @param tag
         * @param containerStart 起始（含）
         * @param containerEnd 结束（不含）
         * @returns {[number, number]|null}
         * @private
         */
        _findDirectChildRange(xml, tag, containerStart, containerEnd) {
            let pos = containerStart;
            let depth = 0;
            while (pos < containerEnd) {
                const lt = xml.indexOf('<', pos);
                if (lt < 0 || lt >= containerEnd) {
                    break;
                }

                if (xml.startsWith('</', lt)) {
                    // 关闭标签，深度-1
                    const gt = xml.indexOf('>', lt + 2);
                    if (gt < 0 || gt >= containerEnd) {
                        break;
                    }
                    depth = Math.max(0, depth - 1);
                    pos = gt + 1;
                    continue;
                }

                // 开始标签，读取名称
                let j = lt + 1;
                let name = '';
                while (j < containerEnd) {
                    const ch = xml[j];
                    if (ch === '>' || /\s/.test(ch) || ch === '/') {
                        break;
                    }
                    name += ch;
                    j++;
                }
                const gt = xml.indexOf('>', j);
                if (gt < 0 || gt >= containerEnd) {
                    break;
                }
                const isSelfClose = xml[gt - 1] === '/';

                if (depth === 0 && name === tag && !isSelfClose) {
                    const closeToken = '</' + tag + '>';
                    const closeIdx = xml.indexOf(closeToken, gt + 1);
                    if (closeIdx >= 0 && closeIdx < containerEnd) {
                        return [lt, closeIdx + closeToken.length];
                    }
                    return null;
                }

                if (!isSelfClose) {
                    depth++;
                }
                pos = gt + 1;
            }
            return null;
        },
        /**
         * 将指定xml字符串中的指定标签的第一个节点替换为指定字符串
         * @param xml xml字符串
         * @param tagPath 标签名或路径（如 a.b.c），不含<>
         * @param s 新的字符串
         * @returns {string} 替换后的xml字符串
         */
        replaceFirstTag(xml, tagPath, s) {
            let start = -1, endExclusive = -1;
            const range = this._findTagPathRange(xml, tagPath);
            if (range) {
                start = range[0];
                endExclusive = range[1];
            }

            if (start >= 0 && endExclusive >= 0) {
                if (s) {
                    xml = xml.substring(0, start) + s + xml.substring(endExclusive);
                } else {
                    xml = xml.substring(0, start).trim() + xml.substring(endExclusive);
                }
            }
            return xml;
        },
        /**
         * 在指定xml字符串中的指定标签的第一个节点后插入指定字符串。指定标签为数组时，依次判断标签是否存在，存在时在其后插入，否则检索下一个标签
         * @param xml xml字符串
         * @param tags 标签名清单
         * @param s 要插入的字符串
         */
        insertAfterTag(xml, tags, s) {
            if (!Array.isArray(tags)) {
                tags = [tags];
            }
            let index = -1;
            for (let tag of tags) {
                let tagEnd = '</' + tag + '>';
                index = xml.indexOf(tagEnd);
                if (index >= 0) {
                    index += tagEnd.length;
                    break;
                }
            }
            if (index >= 0) {
                if (s && !s.startsWith('\n')) {
                    s = '\n' + s;
                }
                xml = xml.substring(0, index) + s + xml.substring(index);
            }
            return xml;
        },
        setTag(xml, replaceTag, tagXml, afterTags, afterIndent = 4) {
            tagXml = tagXml.trim();
            if (this.containsTag(xml, replaceTag)) {
                xml = window.tnx.util.xml.replaceFirstTag(xml, replaceTag, tagXml);
            } else if (tagXml) {
                xml = window.tnx.util.xml.insertAfterTag(xml, afterTags, ' '.repeat(afterIndent) + tagXml);
            }
            if (!xml.endsWith('\n')) {
                xml += '\n';
            }
            return xml;
        },
        toFormattedXml(object, indent, ignoreBlank, keyMapper, valueMapper) {
            let xml = '';
            if (object && !Array.isArray(object) && typeof object === 'object') {
                indent = indent || 0;
                let indentPrefix = ' '.repeat(indent);

                let keys = Object.keys(object);
                for (let key of keys) {
                    let value = object[key];
                    if (value === undefined || value === null) {
                        continue;
                    }
                    // 以英文字母开头的才是有效字段，可转换为合法的xml标签
                    if (/^[A-Za-z]/.test(key)) {
                        let subKeyMapper = undefined;
                        if (typeof keyMapper === 'function') {
                            key = keyMapper(key);
                            if (typeof key !== 'string') {
                                continue;
                            }
                            subKeyMapper = function (subKey) {
                                return keyMapper(key + '.' + subKey);
                            }
                        }
                        if (typeof valueMapper === 'function') {
                            value = valueMapper(value, key);
                        }

                        let items = Array.isArray(value) ? value : [value];
                        for (let item of items) {
                            if (ignoreBlank && typeof item === 'string' && !item.trim()) {
                                continue;
                            }
                            xml += indentPrefix + '<' + key + '>';
                            if (item instanceof Date) {
                                item = item.formatDateTime();
                            }
                            if (typeof item === 'object' && !(item instanceof tnxcore.Enum)) {
                                xml += '\n' + this.toFormattedXml(item, indent + 4, ignoreBlank,
                                    subKeyMapper, valueMapper) + indentPrefix;
                            } else {
                                xml += item;
                            }
                            xml += '</' + key + '>\n';
                        }
                    }
                }
            }
            return xml;
        },
    };

    tnxet.util.command = {
        listeners: {},
        subscribe(name, listener) {
            this.listeners[name] = listener;
        },
        unsubscribe(name) {
            delete this.listeners[name];
        },
    }

    const {ipcRenderer} = require('electron'); // 放在此处以使得在浏览器中可以初始化运行
    ipcRenderer.on(common.channel.command, (event, data) => {
            let listener = tnxet.util.command.listeners[data.name];
            if (listener) {
                let args = data.args || [];
                listener(...args);
            }
        }
    );

    return tnxet;
});
